import inspect
from webapp.fastapi.annotations import *
import os
import traceback
from collections import Iterable
from flask import Response

from server import app
from tools import convert
from tools.convert import translate as t
from tools.linq import linq
from tools.OperationError import OperationError
import re
import importlib


class reflection(object):
    def __init__(self):
        self.libs = {}

    def list(self):
        return map(lambda x: {"name": x[0], "apis": x[1]}, self.libs.items())

    def register(self, module):
        lib = {}
        for name in dir(module):
            attr = getattr(module, name)
            if hasattr(attr, "__module__") and attr.__module__ == module.__name__:
                lib[name.lower()] = attr
        self.libs[module.__name__.split(".")[-1].lower()] = lib
        return lib

    def load(self, path):
        lib = self.libs.get(path.lower())
        if lib:
            return lib
        try:
            module = importlib.import_module("webapp.apis." + path)
            return self.register(module)
        except BaseException as ex:
            print("Load module [path %s] error: %s, %s"
                  % (path, str(ex), traceback.format_exc()))
            return None

    def invoke(self, func, args):
        try:
            return func(**args)
        except TypeError as e:
            message = e.args[0][len(func.__name__) + 2:]
            return OperationError(1, message, e, response_status=400)

    def args(self):
        class _(dict):
            def __getitem__(self, key):
                return self.__dict__[key]

            def __setitem__(self, key, value):
                self.__dict__[key] = value

        return _()


reflect = reflection()


@app.errorhandler(404)
def _404(e=None):
    return '{"message": "接口不存在", "code": 404}', 404, {"Content-Type": "application/json; charset=utf-8"}


@app.errorhandler(500)
def _500(e):
    arr = traceback.format_tb(e.__traceback__)
    arr.reverse()
    return str(e) + "<br />" + "<br />".join(arr), 500


annotationProviders = {
    Json: lambda n, v, r: convert.to_json_obj(v) if isinstance(v, str) else v,
    Request: lambda n, v, r: r,
    Get: lambda n, v, r: r.args,
    Post: lambda n, v, r: r.form,
    Cookies: lambda n, v, r: r.cookies,
    Headers: lambda n, v, r: r.headers
}


def invoke(f):
    if not inspect.isfunction(f):
        return OperationError(404, "接口不存在", response_status=404)
    from flask import request
    args = {}
    signature = inspect.signature(f)

    def transform(annotation, name, val, req):
        if isinstance(annotation, str) or not isinstance(annotation, Iterable):
            annotation = (annotation,)

        if NoTrim not in annotation and isinstance(val, str):
            val = val.strip()

        for ann in iter(annotation):
            if inspect.isfunction(ann):
                val = ann(name, val, req)
            elif ann in annotationProviders:
                val = annotationProviders[ann](name, val, req)

        return val

    form = request.form
    if request.headers.get("Content-Type", "").endswith("/json"):
        json_string = str(request.data, "utf-8")
        form = convert.to_json_obj(json_string)

    for name, value in signature.parameters.items():
        if value.kind == value.VAR_KEYWORD:
            args["get"] = request.args
            args["post"] = request.form
            args["request"] = request
            args["cookies"] = request.cookies
            args["headers"] = request.headers
            continue
        arg = transform(value.annotation, name, form.get(name) or request.args.get(name), request)
        if arg is None and value.default == value.empty:
            return {
                       "code": -1,
                       "message": t('%s: "%s" 必须有值') % (t("参数"), name)
                   }, 400
        if arg:
            args[name] = arg

    return reflect.invoke(f, args)


get_doc_title = re.compile(r"^\s*\:\w+.*$", re.M | re.I)


@app.route("/fastapi_doc", methods=['GET'])
def doc():
    try:
        html = "<ul>"
        for lib in reflect.list():
            html += '<li><a href="/fastapi_doc/%s">/%s</a></li>' % (lib["name"], lib["name"])
        html += "</ul>"
        return html, 200, {"Content-Type": "text/html; charset=utf-8"}
    except BaseException as e:
        return _500(e)


@app.route("/fastapi_doc/<file>", methods=['GET'])
def doc_file(file):
    try:
        lib = reflect.load(file)
        if lib is None:
            return _404()
        html = '<h3><a href="/fastapi_doc">/返回</a></h3><ul>'
        for name, value in lib.items():
            if inspect.isfunction(value):
                html += '<li><a href="/fastapi_doc/%s/%s">/%s/%s</a> - %s</li>' % (
                    file, name, file, name, get_doc_title.sub("", value.__doc__ or "").strip())
        html += "</ul>"
        return html, 200, {"Content-Type": "text/html; charset=utf-8"}
    except BaseException as e:
        return _500(e)


@app.route("/fastapi_doc/<file>/<func>", methods=['GET'])
def doc_api(file, func):
    try:
        lib = reflect.load(file)
        f = lib and lib.get(func)
        if not inspect.isfunction(f):
            return _404()
        signature = inspect.signature(f)
        html = '<h3><a href="/fastapi_doc/%s">/返回</a></h3><div style="font-size:3rem">API地址:<a href="/fastapi/%s/%s">/fastapi/%s/%s</a></div>' % (
            file, file, func, file, func)
        html += '<hr />'
        html += '<pre style="font-size:2rem">\n'
        html += f.__doc__ or "没有文档"
        html += '</pre>\n'
        html += '<hr />'
        html += "<div>参数列表:</div>"
        for name, value in signature.parameters.items():
            if value.kind != value.POSITIONAL_OR_KEYWORD:
                continue
            if value.default == value.empty:
                html += "%s <br />" % (name,)
            else:
                html += "%s , 默认值: %s <br />" % (name, repr(value.default))
        return html, 200, {"Content-Type": "text/html; charset=utf-8"}
    except BaseException as e:
        return _500(e)


@app.route("/fastapi/<file>/<func>", methods=['GET', 'POST'])
def main(file, func):
    try:
        lib = reflect.load(file)
        f = lib and lib.get(func)
        result = invoke(f)
        if isinstance(result, Response):
            return result
    except BaseException as e:
        result = e

    try:
        # 返回值结构
        def parseResult(r):
            if not isinstance(result, tuple):
                return r, 200, {}, None, (), None
            rs = r[1:]
            return r[0], \
                   linq(rs).where(lambda x: isinstance(x, int)).first(200), \
                   linq(rs).where(lambda x: isinstance(x, dict)).first({}), \
                   linq(rs).where(lambda x: x in ("success", "warning", "info", "error")).first(None), \
                   linq(rs).where(lambda x: x in ("refresh", "close")).tolist(), \
                   linq(rs).where(lambda x: isinstance(x, str) and x not in (
                       "success", "warning", "info", "error", "refresh", "close")).first(None)

        result, status, headers, message_type, action, message = parseResult(result)

        # 处理返回值
        if isinstance(result, BaseException):
            if isinstance(result, NotImplementedError):
                status = 501
            else:
                status = -1

            if hasattr(result, "inner_exception"):
                arr = result.inner_exception and traceback.format_tb(result.inner_exception.__traceback__)
            else:
                arr = traceback.format_tb(result.__traceback__)

            if arr:
                arr.reverse()
                trace_stack = "".join(arr)
            else:
                trace_stack = None

            d = linq(result.args).where(lambda x: isinstance(x, dict)).first({})

            server_error = linq(result.args).where(lambda x: isinstance(x, BaseException)).first({}) \
                           or d.get("inner_exception", None)

            if hasattr(result, "response_status"):
                status = result.response_status

            result = {
                "code": d["code"] if "code" in d else 0 if status == 200 else status,
                "message": d["message"] if "message" in d else result.args[0] if len(result.args) > 0 else t(
                    "未知异常"),
            }

            if server_error:
                result["server_error"] = server_error
            if trace_stack:
                result["trace_stack"] = trace_stack

        elif "Content-Type" in headers:
            result = str(result)
        elif isinstance(result, dict) and "code" in result and ("message" in result or "data" in result):
            pass
        elif hasattr(result, "code") and (hasattr(result, "message") or hasattr(result, "data")):
            pass
        else:
            result = {
                "code": 0 if status == 200 else status,
                "data": result
            }

        if not isinstance(result, str):
            result = convert.to_json_string(result)
            headers["Content-Type"] = "application/json; charset=utf-8"
        elif "Content-Type" not in headers:
            headers["Content-Type"] = "application/json; charset=utf-8"

        return result, status, headers
    except BaseException as e:
        return _500(e)


def register(modules):
    if not isinstance(modules, Iterable):
        modules = (modules,)
    for module in modules:
        try:
            if isinstance(module, str):
                reflect.load(module)
            else:
                reflect.register(module)
        except:
            pass
